/**
 * 
 */
package com.gragra.benchmark;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.StringReader;
import java.util.Arrays;
import com.gragra.sampling.PopulationProbabilityAssigner;
import com.gragra.sampling.PopulationSamplingStructure;
import com.gragra.sampling.ThreadedUniformAccess;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
/**
 * @author Christoph Teichmann
 * 20.09.2013
 */
public class MakeBenchmark
{
	/**
	 * 
	 */
	private final int threads;
	/**
	 * 
	 */
	private final FractionAblePopulationStructure[] structs;
	/**
	 * 
	 */
	private final PopulationProbabilityAssigner[] assigner;
	/**
	 * 
	 */
	private final ObjectArrayList<DoubleArrayList>[] maxRatios;
	/**
	 * 
	 */
	private final ObjectArrayList<DoubleArrayList>[] functEvals;
	/**
	 * 
	 */
	private final ObjectArrayList<DoubleArrayList>[] auxFunctEvals;
	/**
	 * 
	 */
	private final ObjectArrayList<DoubleArrayList>[] firstSeen;
	/**
	 * 
	 */
	private final ObjectArrayList<DoubleArrayList>[] secondSeen;
	
	@SuppressWarnings("unchecked")
	public MakeBenchmark(String line, String goldOne, String goldTwo, CreateCorpusAndGoldAssigner[] creats, int its,
										int threads, double smoothing, double weight, int depth, int averagingRounds,
										int samplingSetOff) throws IOException
	{
		this.threads = threads;
		int l = creats.length;
		this.maxRatios = new ObjectArrayList[l];
		this.functEvals = new ObjectArrayList[l];
		this.auxFunctEvals = new ObjectArrayList[l];
		this.firstSeen = new ObjectArrayList[l];
		this.secondSeen = new ObjectArrayList[l];
		this.fillArrays(its,samplingSetOff);
		ThreadedUniformAccess[] tuas = new ThreadedUniformAccess[creats.length];
		for(int i=0;i<tuas.length;++i)
		{tuas[i] = new ThreadedUniformAccess(threads);}
		this.assigner = new PopulationProbabilityAssigner[l];
		this.structs = new FractionAblePopulationStructure[l];
		for(int round=0;round<averagingRounds;++round)
		{
			fillStructuresAndAssigner(creats,line,threads,smoothing,weight,depth,goldOne,goldTwo);
			initialize(tuas);
			for(int iteration=0;iteration<its;++iteration)
			{
				for(int thread=0;thread<threads;++thread)
				{
					for(int i=0;i<l;++i)
					{this.structs[i].resample(this.assigner[i], 1.0, thread, tuas[i]);}
				}
				if((iteration)%samplingSetOff==0)
				{
					int k = iteration/samplingSetOff;
					for(int i=0;i<l;++i)
					{
						this.structs[i].addSample();
						double count1 = this.structs[i].getNumberSeen(goldOne);
						this.firstSeen[i].get(k).add(count1);
						double count2 = this.structs[i].getNumberSeen(goldTwo);
						this.secondSeen[i].get(k).add(count2);
						double ratio = Math.max((count1)/(count2), (count2)/(count1));
						this.maxRatios[i].get(k).add(ratio);
						double funcEvals =  this.assigner[i].getNumberOfEvaluations();
						this.functEvals[i].get(k).add(funcEvals);
						double auxEvals = this.assigner[i].getNumberOfAuxEvals();
						this.auxFunctEvals[i].get(k).add(auxEvals);
					}
				}
			}
			System.out.println("finished one round of sampling benchmarking");
		}
	}
	/**
	 * 
	 * @param tua
	 */
	private void initialize(ThreadedUniformAccess[] tua)
	{
		for(int i = 0; i < this.structs.length; i++)
		{
			PopulationSamplingStructure el = this.structs[i];
			el.initialize(this.assigner[i], tua[i]);
		}
	}
	/**
	 * 
	 * @param to
	 */
	public void writeOutResults(BufferedWriter to) throws IOException
	{
		writeHeader(to);
		for(int it=0;it<this.firstSeen[0].size();++it)
		{writeIt(it,this.threads,to);}
		
	}
	/**
	 * 
	 * @param to
	 */
	private void writeHeader(BufferedWriter to) throws IOException
	{
		to.write("Sampling Step Number,");
		to.write(" Total Samples Drawn");
		for(int i=0;i<this.structs.length;++i)
		{
			String id = this.structs[i].getClass().getName();
			to.write(", function evals mean "+id+",");
			to.write(" function evals variance "+id+",");
			to.write(" auxiliary function evals mean "+id+",");
			to.write(" auxiliary function evals variance "+id+",");
			to.write(" ratio median "+id+",");
			to.write(" ratio third quartile "+id+",");
			to.write(" amount first mean "+id+",");
			to.write(" amount first variance "+id+",");
			to.write(" amount second mean "+id+",");
			to.write(" amount second variance "+id);
		}
	}
	/**
	 * 
	 * @param it
	 * @param to
	 */
	private void writeIt(int it, int threads, BufferedWriter to) throws IOException
	{
		to.newLine();
		to.write(Integer.toString(it+1)+",");
		to.write(Integer.toString((it+1)*threads));
		for(int i=0;i<this.structs.length;++i)
		{
			double mean = this.makeMean(this.functEvals[i].get(it));
			double variance = this.makeDeviation(this.functEvals[i].get(it),mean);
			to.write(","+Double.toString(mean)+",");
			to.write(Double.toString(variance)+",");
			mean = this.makeMean(this.auxFunctEvals[i].get(it));
			variance = this.makeDeviation(this.auxFunctEvals[i].get(it),mean);
			to.write(Double.toString(mean)+",");
			to.write(Double.toString(variance)+",");
			mean = this.makeMedian(this.maxRatios[i].get(it));
			variance = this.makeThirdQuartile(this.maxRatios[i].get(it));
			to.write("<"+Double.toString(mean)+">"+",");
			to.write(Double.toString(variance)+",");
			mean = this.makeMean(this.firstSeen[i].get(it));
			variance = this.makeDeviation(this.firstSeen[i].get(it),mean);
			to.write(Double.toString(mean)+",");
			to.write(Double.toString(variance)+",");
			mean = this.makeMean(this.secondSeen[i].get(it));
			variance = this.makeDeviation(this.secondSeen[i].get(it),mean);
			to.write(Double.toString(mean)+",");
			to.write(Double.toString(variance));
		}
	}
	/**
	 * 
	 * @param doubleArrayList
	 * @return
	 */
	private double makeMean(DoubleArrayList doubleArrayList)
	{
		double sum = 0.0;
		for(int i = 0; i < doubleArrayList.size(); i++)
		{sum += doubleArrayList.getDouble(i);}
		return sum/(double) doubleArrayList.size();
	}
	/**
	 * 
	 * @param doubleArrayList
	 * @param mean
	 * @return
	 */
	private double makeThirdQuartile(DoubleArrayList doubleArrayList)
	{

		double[] arr = doubleArrayList.toArray(new double[doubleArrayList.size()]);
		Arrays.sort(arr);
		return arr[3*(arr.length/4)];
	}
	/**
	 * 
	 * @param doubleArrayList
	 * @param mean
	 * @return
	 */
	private double makeDeviation(DoubleArrayList doubleArrayList, double mean)
	{
		double sum = 0.0;
		for(int i=0;i<doubleArrayList.size();++i)
		{sum += Math.pow((doubleArrayList.getDouble(i)-mean)/mean, 2.0);}
		return (sum/((double) doubleArrayList.size()));
	}
	/**
	 * 
	 * @param doubleArrayList
	 * @return
	 */
	private double makeMedian(DoubleArrayList doubleArrayList)
	{
		double[] arr = doubleArrayList.toArray(new double[doubleArrayList.size()]);
		Arrays.sort(arr);
		return arr[arr.length/2];
	}
	/**
	 * 
	 * @param creats
	 * @param goldOne
	 * @param threads
	 * @param smoothing
	 * @param weight
	 * @param depth
	 * @throws IOException 
	 */
	private void fillStructuresAndAssigner(
			CreateCorpusAndGoldAssigner[] creats, String line, int threads,
			double smoothing, double weight, int depth,String goldOne, String goldTwo) throws IOException
	{
		for(int i=0;i<creats.length;++i)
		{
			StringReader sr = new StringReader(line);
			BufferedReader br = new BufferedReader(sr);
			StringReader srGold = new StringReader(goldOne+"\n"+goldTwo);
			BufferedReader brGold = new BufferedReader(srGold);
			creats[i].makeCorpusAndAssigner(br,brGold,threads,smoothing,weight,depth);
			this.assigner[i] = creats[i].getAssigner();
			this.structs[i] = creats[i].getStructure(0);
		}
	}
	/**
	 * 
	 * @param its
	 * @param samplingSetOff 
	 */
	private void fillArrays(int its, int samplingSetOff)
	{
		its /= samplingSetOff;
		for(int i=0;i<this.maxRatios.length;++i)
		{
			this.fill(this.maxRatios,i,its);
			this.fill(functEvals, i, its);
			this.fill(auxFunctEvals, i, its);
			this.fill(firstSeen, i, its);
			this.fill(secondSeen, i, its);
		}
	}
	/**
	 * 
	 * @param list
	 * @param number
	 * @param its 
	 */
	private void fill(ObjectArrayList<DoubleArrayList>[] list, int number, int its)
	{
		ObjectArrayList<DoubleArrayList> l = list[number] = new ObjectArrayList<DoubleArrayList>();
		for(int it=0;it<its;++it)
		{l.add(new DoubleArrayList());}
	}
}